閉包是一個蠻有趣的功能,顧名思義就是 func 裡面還有一個 func,我們先觀察一下以下程式碼
func adder() func(int) int {
sum := 0
return func(v int) int {
sum += v
return sum
}
}
func main() {
a := adder()
for i := 0; i < 5; i++ {
fmt.Printf("0 + 1 + ... + %d = %d \n", i, a(i))
}
}
輸出結果為:
0 + 1 + ... + 0 = 0
0 + 1 + ... + 1 = 1
0 + 1 + ... + 2 = 3
0 + 1 + ... + 3 = 6
0 + 1 + ... + 4 = 10
這個 func adder() func(int) int {} ,看起來有點複雜,我們先一步步拆解,根據 func 的概念,在 adder() 裡面的表示輸入參數,表示這個函數是沒有輸入參數的,那後面的 func(int) int,表示為 adder() 的輸出,所以就是 adder() 輸出一個 func,該 func 有一個輸入 int 且輸出一個 int,因此 adder() 最後回傳的也必須是 func(int) int,理解了整個架構後,我們就來看看這個 adder() 有什麼特別之處吧。
在 main 裡面的 a 先宣告為 adder() ,然後接下來就是不斷的輸出 a(i),奇怪的是如果依照 func 的概念,每次進入 func ,理論上裡面的變數應該都會是初始預設值,也就是 sum := 0,然後透過 sum += v,得到 sum,所以按照這個概念,a(1)應該要為1,a(2)應該是2,a(3)應該是3...,但很顯然,每次計算完的sum都被保留下來,保留到下次繼續累加,或者用更直接暴力的方式呈現
// func adder() func(int) int {
// sum := 0
// return func(v int) int {
// sum += v
// return sum
// }
// }
// func main() {
// a := adder()
fmt.Println(a(5))
fmt.Println(a(10))
// }
輸出結果為:
5
15
可以非常明顯的看到在 adder() func 中的 sum 會保留上一次的結果,那這就是閉包的功能,那我們可以用這個方式來理解一下到底中間發生了什麼事,當我們一開始創建 a := adder()時,對於 adder() 這個 func 來說,其實真正有被用到的是這個部分
func adder() func(int) int {
sum := 0
// return func(v int) int {
// sum += v
// return sum
// }
}
func main() {
a := adder()
}
那接下來當我們使用 a(5) 與 a(10) 時,其實真正有執行的是這個部分
//func adder() func(int) int {
// sum := 0
return func(v int) int { // 真正在執行的是這個部分
sum += v
return sum
}
//}
func main() {
// a := adder()
a(5)
a(10)
}
因此 sum := 0 這行其實並不會被執行到,所以不會被重置,使得 sum 的值被保存了下來。那我們現在已經有了概念,那來看看這個例子,來猜測一下結果
// func adder() func(int) int {
// sum := 0
// return func(v int) int {
// sum += v
// return sum
// }
// }
// func main() {
a := adder()
fmt.Println(a(5))
fmt.Println(a(10))
a = adder()
fmt.Println(a(5))
fmt.Println(a(10))
// }
究竟這樣的結果會是 5, 15, 5, 15 還是 5, 15, 20, 35 呢?
5
15
5
15
沒錯,結果就是 5, 15, 5, 15 ,原因就是當 a = adder()執行時, sum := 0 再次被執行到,因此 sum 會被重置為 0
func adder() func(int) int {
sum := 0
// return func(v int) int {
// sum += v
// return sum
// }
}
順便附註一下,adder()並不會額外賦予其他記憶體空間
// func adder() func(int) int {
// sum := 0
// return func(v int) int {
// sum += v
// return sum
// }
// }
// func main() {
a := adder()
fmt.Println(a(5), &a)
fmt.Println(a(10), &a)
a = adder()
fmt.Println(a(5), &a)
fmt.Println(a(10), &a)
// }
輸出結果就如你所預測一樣,a 的地址是不會改變的,因此可以確認確實是因為再次執行了adder() 中的 sum := 0,因而重置了 sum 的值,下面附結果證明
5 0x14000120018
15 0x14000120018
5 0x14000120018
15 0x14000120018
也可以換個寫法,結果也是一樣,至於哪種寫法比較好,就看個人喜好了,嘻嘻
type iAdder func(int) (int, iAdder)
func adder2(base int) iAdder {
return func(v int) (int, iAdder) {
return base + v, adder2(base + v)
}
}
func main() {
a2 := adder2(0)
result, a2 := a2(5)
fmt.Println(result)
result, a2 = a2(10)
fmt.Println(result)
}
https://github.com/luckyuho/ithome30-golang/tree/main/day07